package org.awa.demo.opencv.task.kunming;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;
import org.awa.demo.opencv.constant.CarTypeEnum;
import org.awa.demo.opencv.gltf.SoundUtil;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.feature.FeatureCollection;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.grid.Grids;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.index.strtree.STRtree;
import org.opengis.feature.simple.SimpleFeature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.File;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Component
public class KunMingTask {

    private boolean isDebug = false;

    @Autowired
    private org.awa.demo.opencv.sound.SoundUtil soundUtil;

    private int pointSize = 10;
    private int sideLen = 80;
    private double defaultValue = 30;

    public List<GroupItemDto> run() {
        List<GroupItemDto> groupItemList = new ArrayList<>();
        try{
            List<File> geojsonList = getGeojson();
            if(geojsonList.size() == 0){
                return new ArrayList<>();
            }
            for (File file : geojsonList) {
                GroupItemDto groupItem = new GroupItemDto();
                groupItem.setName(file.getName());

                GeoJsonDto geoJsonDto = getFeatureListFromGeoJson(file);

                groupItem.setEnvelope(geoJsonDto.getRange().getGeometry().getEnvelope().toText());
                groupItem.setRoadList(geoJsonDto.getRoadList().parallelStream().map(roadFeatureDto -> {
                    RoadItemDto roadItem = new RoadItemDto();
                    roadItem.setName(roadFeatureDto.getName());
                    roadItem.setLeqt(roadFeatureDto.getLeqt());
                    roadItem.setGeometry(roadFeatureDto.getGeometry().toText());
                    return roadItem;
                }).collect(Collectors.toList()));
                groupItem.setPointList(geoJsonDto.getPointList().parallelStream().map(pointFeatureDto -> {
                    PointItemDto pointItem = new PointItemDto();
                    pointItem.setGeometry(pointFeatureDto.getGeometry().toText());
                    pointItem.setLeqt(pointFeatureDto.getLeqt());
                    return pointItem;
                }).collect(Collectors.toList()));

                /* 噪声算法完成预栅格处理 */
                //四边形预栅格化地区（1米一格）,初始化基础噪声
                List<GridBean> gridBeanList = new ArrayList<>();
                Envelope envelope = geoJsonDto.getRange().getGeometryMercator().getEnvelopeInternal();
                SimpleFeatureSource grid = Grids.createSquareGrid(new ReferencedEnvelope(
                        envelope.getMinX(),envelope.getMaxX(),
                        envelope.getMinY(),envelope.getMaxY(),
                        DefaultGeographicCRS.WGS84),sideLen);//WGS84 说是没用，看看效果再说
                log.info("int x range {} - {}:{}, y range {}-{}:{}",envelope.getMinX(),envelope.getMaxX(),envelope.getMaxX() - envelope.getMinX(),envelope.getMinY(),envelope.getMaxY(),envelope.getMaxY() - envelope.getMinY());
                SimpleFeatureIterator iterator = grid.getFeatures().features();
                while (iterator.hasNext()){
                    SimpleFeature feature = iterator.next();
                    Geometry geometry = (Geometry) feature.getDefaultGeometry();
                    gridBeanList.add(new GridBean(geometry,defaultValue));
                }
                log.info("grid list size:"+gridBeanList.size());

                //建立预栅格的list形式以及STRtree形式存储方式
                STRtree gridTree = new STRtree();
                for (int i = 0; i < gridBeanList.size(); i++) {
                    GridBean gridBean = gridBeanList.get(i);
                    gridTree.insert(gridBean.getGeometry().getEnvelopeInternal(), gridBean);
                }
                gridTree.build();
                log.info("grid tree builded");

                //遍历噪声源，buffer+EnvelopeInternal确定搜索范围，搜索变更的预栅格，遍历追加噪声值、距离和备注
                IInterpolation roadInterpolation = new OutSideLineInterpolation();
                for (int i = 0; i < geoJsonDto.getRoadList().size(); i++) {
                    RoadFeatureDto roadFeatureDto = geoJsonDto.getRoadList().get(i);
                    Envelope buffEnvelope = roadFeatureDto.getGeometryMercator().buffer(2000).getEnvelopeInternal();
                    List<GridBean> effectGridList = gridTree.query(buffEnvelope);
                    log.info("road grid tree query: "+effectGridList.size());
                    for (GridBean effectGridBean : effectGridList) {
                        ValueBean valueBean = roadInterpolation.interpolate(effectGridBean.getGeometry().getInteriorPoint(), roadFeatureDto.getGeometryMercator(), roadFeatureDto.getLeqt());
                        if(valueBean != null){
                            effectGridBean.appendLeqt(valueBean);
                        }
                    }
                }
                log.info("grid road noise called");

                //遍历噪声源，buffer+EnvelopeInternal确定搜索范围，搜索变更的预栅格，遍历追加噪声值、距离和备注
                IInterpolation pointInterpolation = new OutSidePointInterpolation();
                for (int i = 0; i < geoJsonDto.getPointList().size(); i++) {
                    PointFeatureDto pointFeatureDto = geoJsonDto.getPointList().get(i);
                    Envelope buffEnvelope = pointFeatureDto.getGeometryMercator().buffer(2000).getEnvelopeInternal();
                    List<GridBean> effectGridList = gridTree.query(buffEnvelope);
                    log.info("road grid tree query: "+effectGridList.size());
                    for (GridBean effectGridBean : effectGridList) {
                        ValueBean valueBean = pointInterpolation.interpolate(effectGridBean.getGeometry().getInteriorPoint(), pointFeatureDto.getGeometryMercator(), pointFeatureDto.getLeqt());
                        if(valueBean != null){
                            effectGridBean.appendLeqt(valueBean);
                        }
                    }
                }

                log.info("grid point noise called");

                //遍历预栅格，合并计算最终噪声值
                gridBeanList.parallelStream().forEach(GridBean::superposition);

                log.info("grid noise superposited");

                Map<Double, List<GridBean>> collect = gridBeanList.parallelStream().collect(Collectors.groupingBy(gridBean -> {
                    return gridBean.getGeometry().getInteriorPoint().getX();
                }));
                List<Double> xArr = collect.keySet().parallelStream().sorted().collect(Collectors.toList());
                log.info("xArr size:"+xArr.size());
                List<Double> yArr = collect.get(xArr.get(0)).parallelStream().map(gridBean -> {
                    return gridBean.getGeometry().getInteriorPoint().getY();
                }).sorted().collect(Collectors.toList());
                log.info("yArr size:"+yArr.size());

                Double[][] gridValueArr = new Double[yArr.size()][xArr.size()];
                collect.entrySet().parallelStream().forEach(entry -> {
                    Double xValue = entry.getKey();
                    int xIndex = xArr.indexOf(xValue);
                    for (int i = 0; i < entry.getValue().size(); i++) {
                        GridBean gridBean = entry.getValue().get(i);
                        int yIndex = yArr.indexOf(gridBean.getGeometry().getInteriorPoint().getY());
                        gridValueArr[yIndex][xIndex] = gridBean.getLeqt();
                    }
                });
                log.info("noise arr:{},{}",gridValueArr[0].length,gridValueArr.length);

                Double[][] dataArr = BilinearInterpolator.builder().size(5).sideLen(sideLen).dataList(gridValueArr).build().interpolate();
                log.info("bilinear arr:{},{}",dataArr[0].length,dataArr.length);

                saveFile(dataArr);

                List<List<Integer>> dataList = new ArrayList<>();
                for (int y = 0; y < dataArr.length; y++) {
                    Double[] yValueList = dataArr[y];
                    for (int x = 0; x < yValueList.length; x++) {
                        Double value = yValueList[x];

                        List<Integer> pointValue = new ArrayList<>();
                        pointValue.add(x);
                        pointValue.add(y);
                        pointValue.add((int) Math.floor(value));

                        dataList.add(pointValue);
                    }
                }

                log.info("trans list:{}",dataList.size());

                groupItem.setData(new ArrayList<>());
                groupItem.getData().add(dataList);

                groupItemList.add(groupItem);
            }
            /* 双线性插值完成最终栅格化 */
            //重新提取范围EnvelopeInternal，确定长方形边界
            //建立高程二维数组（double[][]），初始化NaN
            //遍历后覆盖预栅格数据
            //二次遍历*4


            //输出：Envelope范围WKT double[1][point size][3],其中最小数据为[x,y,value],以默认值为透明

            /*
            *
            * [{
            *   "envelope":"wkt string",
            *   "data":[[
            *       [0,0,32.6],
            *       [0,1,35.6],
            *       [0,2,35.8],
            *       ...
            *   ]],
            *   "name":"xxx.geojson",
            *   "roadList":[{
            *       "geometry":"wkt string",
            *       "name":"xxxx",
            *       "leqt": 56.6
            *   }...],
            *   "pointList":[{
            *       "geometry":"wkt string",
            *       "leqt": 45.6
            *   }]
            * }...]
            *
            * */
        }catch (Exception e){
            log.info("exceptions: "+e.toString(),e);
        }finally {
            log.info("done");
        }

        return groupItemList;
    }

    private Double randomLeqt() {
        return BigDecimal.valueOf(RandomUtil.randomInt(600, 1100)).divide(BigDecimal.valueOf(10), 1, RoundingMode.HALF_DOWN).doubleValue();
    }

    private Point randomPoint(Geometry range){
        GeometryFactory geometryFactory = new GeometryFactory();
        Envelope envelope = range.getEnvelopeInternal();
        while (true){
            double x = envelope.getMinX() + RandomUtil.randomDouble(1) * envelope.getWidth();
            double y = envelope.getMinY() + RandomUtil.randomDouble(1) * envelope.getHeight();

            Point point = geometryFactory.createPoint(new Coordinate(x, y));
            if(range.covers(point)){
                return point;
            }
        }
    }

    private void log(String msg) {
        if(isDebug) {
            log.info(msg);
        }
    }

    private void log(String format, Object... args) {
        if(isDebug){
            log.info(format, args);
        }
    }

    private void log(String msg, Throwable throwable) {
        if(isDebug){
            log.error(msg + ":" + throwable.toString(), throwable);
        }
    }

//    private List<File> getGeojson() throws Exception{
//        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//        Resource[] resources = resolver.getResources("classpath*:*.geojson");
//        return Arrays.stream(resources).map(resource -> {
//            if(resource.isFile()){
//                try{
//                    return resource.getFile();
//                }catch (Exception e){
//                    log.error("getGeojson 1 error: "+e.toString(),e);
//                    return null;
//                }
//            }else{
//                return null;
//            }
//        }).filter(file -> {
//            return file != null;
//        }).collect(Collectors.toList());
//
//    }

    private List<File> getGeojson() throws Exception{
        List<File> result = new ArrayList<>();

        File file = new File("D:\\demotest\\20250903\\kunming.geojson");
        result.add(file);

        return result;
    }

    private GeoJsonDto getFeatureListFromGeoJson(File geojson) throws Exception {
        GeoJsonDto dto = new GeoJsonDto();
        dto.setRoadList(new ArrayList<>());
        dto.setPointList(new ArrayList<>());

        FeatureJSON fjson = new FeatureJSON();

        fjson.setEncodeFeatureCRS(false);

        FeatureCollection featureCollection = fjson.readFeatureCollection(geojson);
        SimpleFeatureIterator iterator = (SimpleFeatureIterator) featureCollection.features();

        while (iterator.hasNext()){
            SimpleFeature feature = iterator.next();
            Integer type = ((Long) feature.getAttribute("type")).intValue();
            if(type == 1){
                RoadFeatureDto roadFeatureDto = new RoadFeatureDto();

                String name = (String) feature.getAttribute("name");
                roadFeatureDto.setName(name);

                Integer id = ((Long) feature.getAttribute("id")).intValue();
                roadFeatureDto.setId(id);

                Geometry geometryGCG2000 = (Geometry) feature.getAttribute("geometry");
                roadFeatureDto.setGeometry(geometryGCG2000);

                String mercatorCrs = SoundUtil.getMercatorCrs(geometryGCG2000.getInteriorPoint());
                roadFeatureDto.setMercatorCrs(mercatorCrs);

                Geometry geometryMercator = SoundUtil.getMercatorGeometry(geometryGCG2000, "EPSG:4490", mercatorCrs);
                roadFeatureDto.setGeometryMercator(geometryMercator);

                getRoadLeqt(roadFeatureDto);
                dto.getRoadList().add(roadFeatureDto);
            }else{
                RangeFeatureDto rangeFeatureDto = new RangeFeatureDto();

                Geometry geometryGCG2000 = (Geometry) feature.getAttribute("geometry");
                rangeFeatureDto.setGeometry(geometryGCG2000);

                String mercatorCrs = SoundUtil.getMercatorCrs(geometryGCG2000.getInteriorPoint());
                rangeFeatureDto.setMercatorCrs(mercatorCrs);

                Geometry geometryMercator = SoundUtil.getMercatorGeometry(geometryGCG2000, "EPSG:4490", mercatorCrs);
                rangeFeatureDto.setGeometryMercator(geometryMercator);

                Integer id = ((Long) feature.getAttribute("id")).intValue();
                rangeFeatureDto.setId(id);

                dto.setRange(rangeFeatureDto);
            }
        }

        for (int i = 0; i < pointSize; i++) {
            try{
                PointFeatureDto pointFeature = new PointFeatureDto();
                pointFeature.setGeometry(randomPoint(dto.getRange().getGeometry()));
                pointFeature.setMercatorCrs(dto.getRange().getMercatorCrs());
                pointFeature.setGeometryMercator(SoundUtil.getMercatorGeometry(pointFeature.getGeometry(),"EPSG:4490", pointFeature.getMercatorCrs() ));
                pointFeature.setLeqt(RandomUtil.randomBigDecimal(BigDecimal.valueOf(60),BigDecimal.valueOf(110)).setScale(1,RoundingMode.HALF_DOWN).doubleValue());
                pointFeature.setName(UUID.randomUUID().toString().replaceAll("-",""));
                dto.getPointList().add(pointFeature);

            }catch (Exception e){
                log.error("point create failed: "+e.toString(),e);
            }
        }

        return dto;
    }

    //获取道路leqt
    private void getRoadLeqt(RoadFeatureDto roadFeatureDto){
        TrafficDBDto trafficDBDto = TrafficDBDto.randomDto();

        List<Double> leqtList = new ArrayList<>();
        leqtList.add(soundUtil.calLeptByTraffic(trafficDBDto.getCarTrafficFlowShort(),trafficDBDto.getCarAverageSpeedShort(), CarTypeEnum.SMALL_CAR));
        leqtList.add(soundUtil.calLeptByTraffic(trafficDBDto.getCarTrafficFlowMiddle(),trafficDBDto.getCarAverageSpeedMiddle(), CarTypeEnum.MIDDLE_CAR));
        leqtList.add(soundUtil.calLeptByTraffic(trafficDBDto.getCarTrafficFlowLong(),trafficDBDto.getCarAverageSpeedLong(), CarTypeEnum.BIG_CAR));
        roadFeatureDto.setLeqt(soundUtil.soundSuperposition(leqtList));
    }

    private void saveFile(Double[][] resultArr){
        try{
            File file = new File("D://opencv-arr-"+ DateUtil.format(new Date(),"yyyyMMddHHmmssSSS") +".txt");
            file.createNewFile();

            List<String> lines = new ArrayList<>();
            for (int i = 0; i < resultArr.length; i++) {
                Double[] line = resultArr[i];
                String content = Stream.of(line).parallel().map(value -> {
                    try{
                        BigDecimal bigDecimal = BigDecimal.valueOf(value).setScale(1, RoundingMode.HALF_DOWN);
                        DecimalFormat format = new DecimalFormat("000.0");
                        return format.format(bigDecimal.doubleValue());
                    }catch (Exception e){
                        log.error(">>>><<<<< "+value);
                        return "00NAN";
                    }
                }).collect(Collectors.joining(" "));
                lines.add(content);
            }
            FileUtil.appendUtf8Lines(lines,file);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}
